실행컨텍스트와 Hoisting와 TDZ 그리고 Overloading

Hoisting ?

var result = book();
console.log(result, myName); //=> 호이스팅 , marulloc 이 출력된다.

function book() {
  return '호이스팅';
}
var myName = 'marulloc';

일반적인 스크립팅 언어는 위에서부터 아래로 실행되므로,아직 book()함수의 정의가 안되어 있는 첫번째 줄에서는 에러가 발생한다. 그러나 자바스크립트에서는 초기화가 안된 것 같은 변수나 함수에 접근이 가능하다. 이것을 JS의 Hoisting이라고 한다.

실행컨텍스트 생성 단계로 알아보는 Hoisting 원리

실행컨텍스트의 렉시컬환경컴포넌트의 처리에 대해 더 자세히 알아보자

{
  "렉시컬환경컴포넌트_LEC": {
    "환경레코드": {
      "선언적환경레코드": {},
      "오브젝트환경레코드": {}
    },
    "외부 렉시컬 환경 참조": {}
  }
}

JS 엔진은 함수를 만나면, 빈 Function Object를 만들어 Call Stack에 push한다. Function Object에는 함수 내부 코드가 포함되어 있다. 콜스택에서 해당 컨텍스트의 순서가 오면, JS엔진은 실행컨텍스트 초기화를 시작한다.


이해를 돕기위해, 선언표현식, 할당의 개념에 대해 먼저 말하자면
let x = 5 의 경우에 let x선언, 5표현식 =할당이다


이제 실행컨텍스트 초기화 단계를 살펴보자

  1. 선언 단계

    • 코드를 스캔하여, 변수, 함수선언문을 실행컨텍스트의 렉시컬 환경 컴포넌트Key-Value 형태로 등록
    • 변수의 이름(식별자)가 실행컨텍스트에 등록되는 것
    • 함수선언문은 바로 Function Object까지 생성하여 바로 할당된다.
  2. 표현식 처리 및 초기화 단계

    • 코드를 다시 스캔하며, 표현식들을 먼저 처리한다.
    • 등록된 변수들을 위한 메모리 공간을 할당하고 undefined로 초기화한다.
    • 함수 선언문 의 경우 선언 단계에서 이미 Function Object로 초기화 되어 있다.
    • var 변수는 이때 undefined로 초기화된다.
  3. 실행 시작

    • 이제 실행 가능해졌다. 코드를 실행한다.

실행 컨텍스트 생성 단계에서 Context의 미리 변수나 함수 선언문이 초기화 되어 있다.
그래서 실행단계에서 아직 초기화 안되어 있어보이는 변수나 함수에 접근 가능해 진 것이다.
이게 우리가 말하는 Hoisting이다.

let과 const은 왜 호이스팅되지 않는가 - TDZ

let, const 모두 호이스팅은 된다. 다만, 초기화되지 않는다.

실행컨텍스트 초기화 단계에서 var 변수는 undefined로 초기화 되지만,
let, const는 어떤 값으로도 초기화 되지않는다.

letconst는 실제 코드 실행단계에서 할당연산자를 만나야만 초기화가 된다.
그 전에 변수에 접근하려고 하면 ReferenceError가 발생한다.

이렇게 바로 초기화 되지 않고 할당까지 기다리는 상태를 가리켜
Temperal Dead Zone(TDZ, 일시적 사각지대)에 위치한다고 하는 것이다.

함수 표현식은 왜 호이스팅 되지 않을까?

함수 표현식은 변수에 할당하는 방식이다.
함수를 var 변수에 할당한다 할지라도 undefined일 뿐이지, 함수 호출은 불가능하다.
실제 코드 실행단계까지 가서 할당되어야만 실행된다.


JS에서 함수 오버로딩이 안되는 이유

Overloading이란, 함수 이름이 같더라도, 파라미터 수 또는 값의 타입이 다르면 각각의 함수가 존재하는 것이 오버로딩이다. 다른 언어에선 function test(a), funtion test2(a,b)를 다른 함수로 취급한다.

그러나 JS에서는 함수를 {함수명: Function Object} 와 같이 k/v 형태로 선언적 환경 레코드에 저장한다. Execution Context은 객체다. 우리가 객체에 똑같은 key 값을 2개 가질 수 없듯이, 하나의 Execution Context에 함수 이름이 같은 두 개의 다른 Function Object를 가질 수는 없다. 그저 덮어씌워질 뿐이다.

하지만, 타입스크립트로는 함수 오버로딩이 가능하지 않는가 ?

// string 타입 매개변수 1개를 받거나
function greet(name: string): string;
// number 타입 매개변수 1개를 받거나
function greet(age: number): string;
// (또는 다른 조합들...)

타입스크립트의 오버로딩은 컴파일 시점에만 유효하며, 실제 자바스크립트로 변환(컴파일)될 때는 단 하나의 구현체 함수로 합쳐진다.

function greet(arg: string | number): string {
  if (typeof arg === 'string') {
    return `Hello, ${arg}`;
  } else if (typeof arg === 'number') {
    return `You are ${arg} years old`;
  }
  // 모든 경우를 다루지 않으면 에러 발생 가능성 있음
  throw new Error('Invalid argument');
}

// 사용 예시
console.log(greet('Alice')); // Hello, Alice (string 시그니처 사용)
console.log(greet(30)); // You are 30 years old (number 시그니처 사용)

컴파일 시점에 어떤 시그니처로 함수를 호출했는지 정확하게 타입 검사를 해준다. 개발자에게 오버로딩된 것처럼 보이는 강력한 타입 안전성을 제공하지만, 최종 결과물(JS 코드)은 여전히 단일 함수인 것이다.